Skip to content

Conversation

@SwayamInSync
Copy link
Member

closes #216

I took the reference for implementing as_integer_ratio from CPython's implementation

}
}

// this is thread-unsafe
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ngoldbaum precisely, Sleef_snprintf here is thread-unsafe, I believe just GIL won't help here as this is C routine and GIL only protects the Python objects.
Not 100% sure so need your opinion that whether GIL would be enough or we can lock this region with pthread_mutex

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that means that it's not safe to concurrently call Sleef_snprintf simultaneously in two threads (e.g. it's not re-entrant)?

In that case, yeah, I think you need a global lock.

I'd avoid using pthreads directly because then you'd need to do something else on Windows. Instead, I'd use PyMutex on Python 3.13 and newer and PyThread_type_lock on 3.12 and older. See e.g. the use of lapack_lite_lock in numpy/linalg/umath_linalg.cpp in NumPy, which solves a similar problem with the non-reentrant lapack_lite library.

You could also switch to C++ and use a C++ standard library but then you need to be careful you don't deadlock with the GIL by making sure you release it before doing any possibly blocking calls. The built-in lock types have deadlock protection against the GIL so you don't need to go through that trouble.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it'd be nice to add a multithreaded test for this as well. You can look at test_multithreading.py in NumPy for some patterns to use for multithreaded tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a multithreaded test for now only for testing this. Will push the lock after the tests get done

@SwayamInSync
Copy link
Member Author

It seems not to be crashing somehow @ngoldbaum can you check the test_multithreading.py here. If looks good then I can drop the lock commit and we can merge it

@ngoldbaum
Copy link
Member

Did you try running under TSan? You'll need to build python, numpy, SLEEF, and numpy-quaddtype all with the same compiler/sanitizer stack. See https://py-free-threading.github.io/thread_sanitizer/

@SwayamInSync
Copy link
Member Author

Do we need to add this in CI?

@ngoldbaum
Copy link
Member

I don't think so, a one-off local test every now and then is fine. It may make sense to add TSan testing along with supporting the free-threaded build and more extensive multithreaded testing, but not until a lot of work has happened towards that.

@SwayamInSync
Copy link
Member Author

cool, so I can push the lock adding commit

@SwayamInSync
Copy link
Member Author

SwayamInSync commented Nov 1, 2025

Did you try running under TSan? You'll need to build python, numpy, SLEEF, and numpy-quaddtype all with the same compiler/sanitizer stack. See py-free-threading.github.io/thread_sanitizer

Did this on macos and running Pytest didn't give any TSan warnings for test_multithreading.py, test_quaddtype.py there were 3 regarding BLAS ops which I'll handle separately in qblas

@SwayamInSync
Copy link
Member Author

SwayamInSync commented Nov 1, 2025

I'll push the code with the commented options for building with TSan so that in near future, anyone wants just uncomment and test

@SwayamInSync
Copy link
Member Author

@jorenham this stub test failure can be fix in your #218 ?

@jorenham
Copy link
Member

jorenham commented Nov 1, 2025

@jorenham this stub test failure can be fix in your #218 ?

Yes; stubtest does what it's supposed to do now 🎉

The errors can easily be fixed by copying these method stubs over from numpy:
https://github.com/numpy/numpy/blob/779929c42dd5dfa32f07f1311233abea9f026310/numpy/__init__.pyi#L4976-L4977

@SwayamInSync
Copy link
Member Author

@ngoldbaum is this ready to merge?

@SwayamInSync
Copy link
Member Author

@jorenham can you please take a look at this error in validating static types?

@jorenham
Copy link
Member

jorenham commented Nov 1, 2025

@jorenham can you please take a look at this error in validating static types?

The added methods need to be decorated with @override, imported from typing_extensions

@@ -1,5 +1,5 @@
from typing import Any, Literal, TypeAlias, final, overload

import builtins
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the explicit import? We use bool without the builtins.bool elsewhere in the stubs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied from the NumPy's stubs, I think both are just same.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in numpy it's needed because bool is shadowed by np.bool. But that shouldn't be problem here, so no need for the builtins._

@ngoldbaum
Copy link
Member

I try to avoid looking at code on weekends - I'll look at this next week.

Copy link
Member

@ngoldbaum ngoldbaum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for taking a little while to look at this. I wanted to have time to run TSan testing locally.

For what it's worth, on the free-threaded build, I see a data race inside Sleef_iunordq1 on a global value in the new multithreaded test you added. Possibly this is shibatch/sleef#560. We should probably report it upstream as a bug.

@SwayamInSync
Copy link
Member Author

That's great capture, I was using the SLEEF from the subproject itself, thanks a lot @ngoldbaum
I will resolve them and will add a section in README on building with TSan.

Regarding SLEEF (I didn't know pytorch uses it) but in QBLAS I am also using vectorized functions. I might need to evaluate this part extensively as from the related issues and this one itself.
Locking everytime can hardly damage the performance

We are on SLEEF v3.8 (LTS is 3.9, but focussed on DFT not Quad) in short optimal fixes might can take time, but the current ones are resolvable

@SwayamInSync
Copy link
Member Author

@ngoldbaum
Reading the TSan warning, it seems the issue is lazy CPU feature detection. The Sleef_iunordq1 is a wrapper that calls the best available function as per CPU whose pointer is stored in pnt_iunordq1 via some dispatching mechanism that triggers inside disp_iunordq1 and in order to keep it cached this optimal function pointer (pnt_iunordq1) is given global state.

So I think the workflow would be: both the T14 and T74 threads triggered Sleef_iunordq1 **for the first time simultaneously before the function pointer was properly initialized, which leads both dispatchers to race while trying to update this pointer.

I will report this to SLEEF, and I think till then in ours we have 2 options

  • lock (this will be the ultimate gurantee)
  • init all the ops during module initialization (this should let all the init of all global pointers) something like
PyMODINIT_FUNC
PyInit_quaddtype(void)
{
    PyObject *m;
    
    // ... other initialization ...
    
    // SLEEF init
    Sleef_quad dummy = sleef_q(1LL, 0ULL, 0);
    Sleef_quad dummy2 = sleef_q(2LL, 0ULL, 0);
    int dummy_int;
    
    // Warmup all SLEEF functions you use
    Sleef_iunordq1(dummy, dummy2);
    Sleef_icmpgeq1(dummy, dummy2);
    Sleef_fabsq1(dummy);
    Sleef_frexpq1(dummy, &dummy_int);
    Sleef_cast_from_doubleq1(1.0);    
    return m;
}

This might not gurantee the other races if function implementation itself had issues

@SwayamInSync
Copy link
Member Author

Interesting, I am somehow still now able to see the race condition on x86-64 machine

~/temp/OSS/numpy-user-dtypes$ TSAN_OPTIONS="verbosity=1" pytest quaddtype/tests/test_multithreading.py -s 2>&1 | head -20
==4190773==Installed the sigaction for signal 11
==4190773==Installed the sigaction for signal 7
==4190773==Installed the sigaction for signal 8
***** Running under ThreadSanitizer v3 (pid 4190773) *****
============================= test session starts ==============================
platform linux -- Python 3.14.0+, pytest-8.4.2, pluggy-1.6.0
rootdir: /home/ssingh/temp/OSS/numpy-user-dtypes/quaddtype
configfile: pyproject.toml
collected 1 item

quaddtype/tests/test_multithreading.py .

============================== 1 passed in 11.00s ==============================
Stats: SizeClassAllocator64: 20M mapped (7M rss) in 1967503 allocations; remains 3666
  01 (    16): mapped:    256K allocs:    6784 frees:    6400 inuse:    384 num_freed_chunks   16000 avail:  16384 rss:      8K releases:      2 last released:    248K region: 0x720400000000
  02 (    32): mapped:    768K allocs:  830208 frees:  829952 inuse:    256 num_freed_chunks   24320 avail:  24576 rss:    592K releases:     10 last released:    180K region: 0x720800000000
  03 (    48): mapped:    256K allocs:    6528 frees:    6400 inuse:    128 num_freed_chunks    5333 avail:   5461 rss:     12K releases:      2 last released:    240K region: 0x720c00000000
  04 (    64): mapped:    256K allocs:     256 frees:       0 inuse:    256 num_freed_chunks    3840 avail:   4096 rss:     16K releases:      0 last released:      0K region: 0x721000000000
  05 (    80): mapped:    256K allocs:     128 frees:       0 inuse:    128 num_freed_chunks    3148 avail:   3276 rss:      8K releases:      0 last released:      0K region: 0x721400000000
  06 (    96): mapped:    256K allocs:     128 frees:       0 inuse:    128 num_freed_chunks    2602 avail:   2730 rss:      4K releases:      0 last released:      0K region: 0x721800000000

@ngoldbaum
Copy link
Member

I am somehow still now able to see the race condition on x86-64 machine

Do you mean you're not able to trigger it? Certain kinds of data races are only possible on ARM machines. x86_64 CPUs don't support weak memory ordering.

IMO you shouldn't do anything special inside quaddtype to handle this problem. I think the race might be benign in that both threads will end up writing the same result to the function pointer. Instead, I'd report it upstream, ideally with a reproducer written using e.g. pthreads so they can quickly reproduce the issue without installing quaddtype.

So I'd say to go ahead and merge this as-is without implementing your fix to initialize the pointers in module initialization.

@SwayamInSync
Copy link
Member Author

Yeah I just setup on my mac and ran, now got a lot of races and all related to the dispatching of Sleef functions like
pnt_floorq1, pnt_mulq1_u05, pnt_icmpeqq1, pnt_fabsq1, pnt_divq1_u05
They all represent the global pointer to store the machine corresponding dispatched routine

@SwayamInSync
Copy link
Member Author

So I'd say to go ahead and merge this as-is without implementing your fix to initialize the pointers in module initialization.

Sure, I'll resolve the other comments and README section for TSan build and then merge it in

@SwayamInSync
Copy link
Member Author

Do you mean you're not able to trigger it? Certain kinds of data races are only possible on ARM machines. x86_64 CPUs don't support weak memory ordering.

Right, I also think that since my x86-64 machine supports __float128 leading SLEEF fallback to libquadmath and no dispatching required in that case.

@SwayamInSync
Copy link
Member Author

Cool @ngoldbaum if you take a look at the README then this is good to merge

@ngoldbaum ngoldbaum merged commit 8487df3 into numpy:main Nov 7, 2025
11 checks passed
@SwayamInSync SwayamInSync deleted the 216 branch November 7, 2025 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QuadPrecision.is_integer() and QuadPrecision.as_integer_ratio()

4 participants